package com.maiko.maikoaiagent.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 违禁词校验 Advisor
 * 该类用于拦截用户的聊天请求，检测用户输入中是否包含违禁词。
 * 违禁词列表来源于 https://gitee.com/crazypoo/badwords 。
 *
 * 该Advisor实现了 CallAroundAdvisor 和 StreamAroundAdvisor 接口，
 * 既可以处理普通请求，也支持流式请求的校验。
 *
 * 使用时在调用链中优先执行，拦截含有违禁词的请求，抛出异常阻止后续执行。
 */
@Slf4j
public class ProhibitedWordAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

    /**
     * 默认违禁词文件路径（类路径下）
     */
    private static final String DEFAULT_PROHIBITED_WORDS_FILE = "prohibited/prohibited-words.txt";

    /**
     * 加载的违禁词列表
     */
    private final List<String> prohibitedWords;

    /**
     * 默认构造器，从默认违禁词文件加载违禁词列表
     */
    public ProhibitedWordAdvisor() {
        this.prohibitedWords = loadProhibitedWordsFromFile(DEFAULT_PROHIBITED_WORDS_FILE);
        log.info("初始化违禁词Advisor，违禁词数量: {}", prohibitedWords.size());
    }

    /**
     * 带参数构造器，从指定文件加载违禁词列表
     *
     * @param prohibitedWordsFile 违禁词文件路径（类路径）
     */
    public ProhibitedWordAdvisor(String prohibitedWordsFile) {
        this.prohibitedWords = loadProhibitedWordsFromFile(prohibitedWordsFile);
        log.info("初始化违禁词Advisor，违禁词数量: {}", prohibitedWords.size());
    }

    /**
     * 从类路径下指定文件读取违禁词，逐行加载到列表中。
     * 读取过程中忽略空行，去除多余空白。
     *
     * @param filePath 违禁词文件路径（类路径）
     * @return 违禁词列表，失败返回空列表
     */
    private List<String> loadProhibitedWordsFromFile(String filePath) {
        try {
            var resource = new ClassPathResource(filePath);
            var reader = new BufferedReader(
                    new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));

            // 过滤空行，去除首尾空白后收集为列表
            List<String> words = reader.lines()
                    .filter(StringUtils::hasText)
                    .map(String::trim)
                    .collect(Collectors.toList());

            log.info("从文件 {} 加载违禁词 {} 个", filePath, words.size());
            return words;
        } catch (Exception e) {
            log.error("加载违禁词文件 {} 失败", filePath, e);
            return new ArrayList<>();
        }
    }

    /**
     * Advisor 名称，通常返回类名
     */
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    /**
     * Advisor 执行顺序，值越小优先级越高
     * 这里设为 -100，确保违禁词检测优先于其他Advisor
     */
    @Override
    public int getOrder() {
        return -100;
    }

    /**
     * 检查请求中的用户文本是否包含违禁词
     * 若包含，则抛出异常，阻止请求继续执行
     *
     * @param request 待检测请求
     * @return 如果不包含违禁词，返回原请求对象
     */
    private AdvisedRequest checkRequest(AdvisedRequest request) {
        String userText = request.userText();
        if (containsProhibitedWord(userText)) {
            log.warn("检测到违禁词在用户输入中: {}", userText);
            throw new ProhibitedWordException("用户输入包含违禁词");
        }
        return request;
    }

    /**
     * 判断文本中是否包含任何违禁词（不区分大小写）
     *
     * @param text 需要检测的文本
     * @return 包含返回 true，否则 false
     */
    private boolean containsProhibitedWord(String text) {
        if (!StringUtils.hasText(text)) {
            return false;
        }

        // 遍历违禁词列表，检测是否包含其中任何一个词
        for (String word : prohibitedWords) {
            if (text.toLowerCase().contains(word.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 处理普通请求调用，调用链前置执行违禁词检查。
     * 若检测通过则继续执行后续Advisor或实际调用。
     */
    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        // 先检查请求，若异常则抛出，终止调用链
        return chain.nextAroundCall(checkRequest(advisedRequest));
    }

    /**
     * 处理流式请求调用，调用链前置执行违禁词检查。
     * 若检测通过则继续执行后续Advisor或实际调用。
     */
    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        // 先检查请求，若异常则抛出，终止调用链
        return chain.nextAroundStream(checkRequest(advisedRequest));
    }

    /**
     * 自定义违禁词检测异常，用于在检测到违禁词时抛出，终止请求执行。
     */
    public static class ProhibitedWordException extends RuntimeException {
        public ProhibitedWordException(String message) {
            super(message);
        }
    }
}
